﻿/*
Copyright © 2010 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Faz.Dependencies.Controls
{
    /// <summary>
    /// A visual component used to display the header of the reference part
    /// of a <see cref="DependencyView"/>.
    /// </summary>
    /// <seealso cref="DependencyView"/>
    public class DependencyViewResult : Panel
    {
        /// <summary>
        /// Identifies the <see cref="DependencyViewResult.Rows"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty RowsProperty =
            DependencyProperty.Register("Rows", typeof(IEnumerable), typeof(DependencyViewResult));

        /// <summary>
        /// Identifies the <see cref="DependencyViewResult.Columns"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register("Columns", typeof(IEnumerable), typeof(DependencyViewResult));

        /// <summary>
        /// The list of the children sorted by column.
        /// </summary>
        private IDictionary<int, ICollection<DependencyViewResultItem>> itemsByColumn;

        /// <summary>
        /// The list of the children sorted by row.
        /// </summary>
        private IDictionary<int, ICollection<DependencyViewResultItem>> itemsByRow;

        /// <summary>
        /// A value indicating whether the children should be regenerated.
        /// </summary>
        private bool childrenAreInvalid;

        /// <summary>
        /// Initializes static members of the DependencyViewResult class.
        /// </summary>
        [SuppressMessage(
            "Microsoft.Performance",
            "CA1810:InitializeReferenceTypeStaticFieldsInline",
            Justification = "This static constructor is required for WPF initialization")]
        static DependencyViewResult()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(DependencyViewResult),
                new FrameworkPropertyMetadata(typeof(DependencyViewResult)));
        }

        /// <summary>
        /// Initializes a new instance of the DependencyViewResult class.
        /// </summary>
        public DependencyViewResult()
        {
            Trace.Default.TraceVerbose("DependencyViewResult.ctor()");

            this.itemsByColumn = new Dictionary<int, ICollection<DependencyViewResultItem>>();
            this.itemsByRow = new Dictionary<int, ICollection<DependencyViewResultItem>>();
            this.childrenAreInvalid = true;
        }

        /// <summary>
        /// Gets the rows used to generate the dependency table.
        /// </summary>
        public IEnumerable Rows
        {
            get { return (IEnumerable)GetValue(RowsProperty); }
            private set { SetValue(RowsProperty, value); }
        }

        /// <summary>
        /// Gets the columns used to generate the dependency table.
        /// </summary>
        public IEnumerable Columns
        {
            get { return (IEnumerable)GetValue(ColumnsProperty); }
            private set { SetValue(ColumnsProperty, value); }
        }

        /// <summary>
        /// Gets the encapsulating <see cref="ListView"/> instance.
        /// </summary>
        protected ListView ListView
        {
            get { return this.TemplatedParent as ListView; }
        }

        /// <summary>
        /// Gets the associated <see cref="DependencyView"/> instance.
        /// </summary>
        protected DependencyView DependencyView
        {
            get { return this.ListView.View as DependencyView; }
        }

        /// <summary>
        /// Defines the mimimum size of the panel to display its elements.
        /// </summary>
        /// <param name="availableSize">The available size.</param>
        /// <returns>The required size.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            this.GenerateVisualChildren();

            Size resultSize = new Size();

            foreach (UIElement child in this.Children)
            {
                child.Measure(availableSize);
                resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
                resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
            }

            // Ensure that the result doesn't contains any Infinity value
            resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ?
                resultSize.Width : availableSize.Width;

            resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ?
                resultSize.Height : availableSize.Height;

            return resultSize;
        }

        /// <summary>
        /// Places the visual children of the panel.
        /// </summary>
        /// <param name="finalSize">The final size of the panel.</param>
        /// <returns>The used size.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            int rowCount = this.Rows.Cast<object>().Count();
            int columnCount = this.Columns.Cast<object>().Count();

            if (rowCount == 0 || columnCount == 0)
            {
                return finalSize;
            }

            double rowStep = finalSize.Height / rowCount;
            double columnStep = finalSize.Width / columnCount;

            int columnNumber = 0;
            double x = 0.0;
            double y = 0.0;
            foreach (UIElement element in this.Children)
            {
                element.Arrange(new Rect(new Point(x, y), new Size(columnStep, rowStep)));
                x += columnStep;
                columnNumber++;
                if (columnNumber >= columnCount)
                {
                    columnNumber = 0;
                    x = 0.0;
                    y += rowStep;
                }
            }

            return finalSize;
        }

        /// <summary>
        /// Called when the component is initialized.
        /// </summary>
        /// <param name="e">The <see cref="RoutedEventArgs"/> that contains the event data.</param>
        protected override void OnInitialized(EventArgs e)
        {
            Trace.Default.TraceVerbose("DependencyViewResult.OnInitialized()");

            this.SetBinding(RowsProperty, new Binding("ItemsSource") { Source = this.ListView });
            this.SetBinding(ColumnsProperty, new Binding("References") { Source = this.DependencyView });

            this.ListView.SelectionChanged += new SelectionChangedEventHandler(this.OnListViewSelectionChanged);

            base.OnInitialized(e);
        }

        /// <summary>
        /// Called when a property changed - used to catch any change on
        /// <see cref="DependencyViewResult.Rows"/> or <see cref="DependencyViewResult.Columns"/>.
        /// </summary>
        /// <param name="e">The detail of the event.</param>
        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == DependencyViewResult.RowsProperty || e.Property == DependencyViewResult.ColumnsProperty)
            {
                INotifyCollectionChanged collection = e.NewValue as INotifyCollectionChanged;
                if (collection != null)
                {
                    collection.CollectionChanged += this.OnSourceCollectionChanged;
                }

                this.InvalidateMeasure();
            }

            base.OnPropertyChanged(e);
        }

        /// <summary>
        /// Called when one of the <see cref="DependencyViewResult.Rows"/> or <see cref="DependencyViewResult.Columns"/>
        /// collection is updated - invalidate the measure of the item.
        /// </summary>
        /// <param name="sender">The parameter is not used.</param>
        /// <param name="e">The parameter is not used.</param>
        private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.InvalidateVisualChildren();
            this.InvalidateMeasure();
        }

        /// <summary>
        /// Invalids the current Children and ensure that the GenerateVisualChildren method
        /// can work properly.
        /// </summary>
        private void InvalidateVisualChildren()
        {
            this.childrenAreInvalid = true;
        }

        /// <summary>
        /// Generates all the visual children of the instance based on
        /// <see cref="DependencyViewResult.Rows"/> and <see cref="DependencyViewResult.Columns"/>.
        /// </summary>
        private void GenerateVisualChildren()
        {
            Trace.Default.TraceVerbose("DependencyViewResult.GenerateVisualChildren()");

            if (!this.childrenAreInvalid)
            {
                return;
            }

            this.Children.Clear();
            this.itemsByRow.Clear();
            this.itemsByColumn.Clear();

            if (this.Rows == null || this.Columns == null)
            {
                return;
            }

            for (int i = 0; i < this.Rows.Cast<object>().Count(); i++)
            {
                this.itemsByRow.Add(i, new LinkedList<DependencyViewResultItem>());
            }

            for (int i = 0; i < this.Columns.Cast<object>().Count(); i++)
            {
                this.itemsByColumn.Add(i, new LinkedList<DependencyViewResultItem>());
            }

            int rowNumber = 0;
            foreach (object value in this.Rows)
            {
                int columnNumber = 0;
                IEnumerable<object> dependencies = this.DependencyView.ExtractDependencies(value);
                foreach (object reference in this.Columns)
                {
                    DependencyViewResultItem item = new DependencyViewResultItem();

                    bool result = dependencies != null && dependencies.Contains(reference);
                    item.Content = result;

                    item.SetBinding(DependencyViewResultItem.StyleProperty, new Binding("ResultContainerStyle") { Source = this.DependencyView });
                    item.SetBinding(DependencyViewResultItem.ContentStringFormatProperty, new Binding("ResultStringFormat") { Source = this.DependencyView });
                    item.SetBinding(DependencyViewResultItem.ContentTemplateProperty, new Binding("ResultTemplate") { Source = this.DependencyView });
                    item.SetBinding(DependencyViewResultItem.ContentTemplateSelectorProperty, new Binding("ResultTemplateSelector") { Source = this.DependencyView });

                    this.Children.Add(item);
                    this.itemsByColumn[columnNumber].Add(item);
                    this.itemsByRow[rowNumber].Add(item);

                    columnNumber++;
                }

                rowNumber++;
            }
        }

        /// <summary>
        /// Called when the selection changed on the parent list to udpate
        /// the status of the result elements.
        /// </summary>
        /// <param name="sender">The parameter is not used.</param>
        /// <param name="e">The detail of the event.</param>
        private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            bool vertical = e.OriginalSource is DependencyViewReference;
            ListView reference = e.OriginalSource as ListView;

            foreach (object item in e.RemovedItems)
            {
                int index = reference.Items.IndexOf(item);
                if (index == -1)
                {
                    // Manage the case when the selected item is no more part of the list
                    continue;
                }
                else if (vertical)
                {
                    foreach (DependencyViewResultItem visualItem in this.itemsByColumn[index])
                    {
                        visualItem.IsVerticallySelected = false;
                    }
                }
                else
                {
                    foreach (DependencyViewResultItem visualItem in this.itemsByRow[index])
                    {
                        visualItem.IsHorizontallySelected = false;
                    }
                }
            }

            foreach (object item in e.AddedItems)
            {
                int index = reference.Items.IndexOf(item);
                if (vertical)
                {
                    foreach (DependencyViewResultItem visualItem in this.itemsByColumn[index])
                    {
                        visualItem.IsVerticallySelected = true;
                    }
                }
                else
                {
                    foreach (DependencyViewResultItem visualItem in this.itemsByRow[index])
                    {
                        visualItem.IsHorizontallySelected = true;
                    }
                }
            }
        }
    }
}
